#include "main.h"
#include "template_lua.h"

#include <libgen.h>
#include <lua5.1/lua.h>
#include <lua5.1/lualib.h>
#include <lua5.1/lauxlib.h>

static const char *tag_name = LTMPL_NAME;
const tmpl_t lua_tpl = { LTMPL_NAME, lua_preinit, lua_init, lua_free, lua_cleanup, lua_setval, lua_getptr, lua_result, lua_request };
__NCTLS lua_State *L = NULL;
static tmpl_data_t *data = NULL;


static void
lua_disable_functions(lua_State *__L) {
  int i = 0;
  char *tmp, *forbidden[] = { "io", "loadfile", "os", "debug", "loadstring", "setfenv", "getfenv",  NULL };

  while ((tmp = forbidden[i++])) {
    lua_pushnil(__L);
    lua_setglobal(__L, tmp);
  }
}

#ifdef ENABLE_LUADBG
void printTable(lua_State *L)
{
  lua_pushnil(L);
  while(lua_next(L, -2) != 0) {
    if (lua_isstring(L, -1))
      DD("%s = %s", lua_tostring(L, -2), lua_tostring(L, -1))
    else if(lua_isnumber(L, -1))
      DD("%s = %g", lua_tostring(L, -2), lua_tonumber(L, -1))
    lua_pop(L, 1);
  }
}
static void stackDump(lua_State *ls) {
  int i, top;

  top = lua_gettop(ls);
  if (top > 1) DD2("LUA_STACK_DUMP");
  for (i = 1; i <= top; i++) {
  /* repeat for each level */
    int t = lua_type(ls, i);
    switch (t) {
      case LUA_TSTRING:  /* strings */
        DD("LuaStack(%d): '%s'", i, lua_tostring(ls, i));
        break;
      case LUA_TBOOLEAN:  /* booleans */
        DD("LuaStack(%d): %s", i, (lua_toboolean(ls, i) ? "true" : "false"));
        break;
      case LUA_TNUMBER:  /* numbers */
        DD("LuaStack(%d): %g", i, lua_tonumber(ls, i));
        break;
      case LUA_TTABLE:
        DD("LuaStack(%d): Table", i);
        printTable(ls);
        break;
      default:  /* other values */
        DD("LuaStack(%d): %s", i, lua_typename(ls, t));
        break;
    }
  }
}
#endif

static int
lua_log(lua_State *__L) {
  int ltype;
  log_level_t llvl;

  if (lua_gettop(__L) != 2) return (-1);
  if (lua_type(__L, 1) != LUA_TNUMBER) return (-2);
  if (lua_type(__L, 2) != LUA_TSTRING) return (-3);
  ltype = lua_tonumber(__L, 1);
  switch (ltype) {
    case log_err: llvl = log_err; break;
    case log_info: llvl = log_info; break;
    case log_warn: llvl = log_warn; break;
    case log_debug: llvl = log_debug; break;
    default: llvl = log_not;
  }
  LOG(llvl, "%s", lua_tostring(__L, 2));
  return (0);
}

int
lua_preinit(void) {
  DD2("lua preinit");
  data = read_template_files(LTMPL_NAME);
  if (!data) return (TMPLERR_NOFILES);
  return (TMPLERR_OK);
}

int
lua_init(void) {
  tmpl_data_t *tmp;

  DD2("lua init");
  if ((L = lua_open()) != NULL) {
    luaL_openlibs(L);
    lua_register(L, "log", lua_log);
    lua_disable_functions(L);
    tmp = data;
    if (!tmp) return (TMPLERR_FAILED);
    while (tmp->path && tmp->data) {
      I("compile %s ..", basename((char *)get_string(tmp->path)));
      if (luaL_loadbuffer(L, get_string(tmp->data), get_strlen(tmp->data), basename((char *)get_string(tmp->path))) || lua_pcall(L, 0, 0, 0)) E("error loading file: %s", lua_tostring (L, -1));
      lua_getglobal(L, "init");
      if (lua_pcall (L, 0, 0, 0)) E("error running script init function: %s", lua_tostring (L, -1));
      tmp = tmp->next;
    }
    lua_newtable(L);
    lua_setglobal(L, LUA_TBLNAME);
    return (TMPLERR_OK);
  } else {
    return (TMPLERR_FAILED);
  }
}

int
lua_free(void) {
  DD2("lua shutdown");
  if (data) cleanup_template(data);
  return (TMPLERR_OK);
}

int
lua_cleanup(void) {
  DD2("lua cleanup");
  lua_close(L);
  return (TMPLERR_OK);
}

static void
setfield(const char *key, const char *value) {
  lua_pushstring(L, key);
  lua_pushstring(L, value);
  lua_settable(L, -3);
}

int
lua_setval(const tmpl_data_t *td __attribute__((__unused__)), const char *key, const char *value) {
  lua_getglobal(L, LUA_TBLNAME);
  setfield(key, value);
  return (0);
}

tmpl_data_t *
lua_getptr(const char *filename)
{
  char *loc;
  tmpl_data_t *dt;

  if (!data) return (NULL);
  dt = data;
  while (dt->path && dt->data) {
    asprintf(&loc, "%s/%s.%s", tmpl_dir, filename, tag_name);
    if (strcmp(loc, get_string(dt->path)) == 0) {
      free(loc);
      return (dt);
    }
    free(loc);
    dt = dt->next;
  }
  W("template '%s.%s' not found", filename, STMPL_FSUFFIX);
  return (NULL);
}

string_t *
lua_result(const tmpl_data_t *td, string_t **str_ptr) {
  lua_getglobal(L, "result");
  if (lua_pcall (L, 0, 1, 0)) W("%s: error running script result function: %s", get_string(td->path), lua_tostring (L, -1));
  if (!lua_isstring(L, -1)) {
    W("%s: function result must return a string", get_string(td->path));
  } else {
    add_string(*str_ptr, lua_tostring(L, -1));
    lua_pop(L, 1);
  }
#ifdef ENABLE_LUADBG
  stackDump(L);
#endif
  return (*str_ptr);
}

void
lua_request(const tmpl_data_t *td, const request_t *req)
{
  lua_setval(td, KEY_QRY, req->qry);
  lua_setval(td, KEY_URI, req->doc_uri);
}
